package com.ruoci.redis.specialcontroller;

import cn.hutool.core.date.DateUtil;
import com.ruoci.redis.constants.BusinessConstants;
import com.ruoci.redis.entity.Sign;
import com.ruoci.redis.response.BasicResponse;
import com.ruoci.redis.response.ResponseCode;
import com.ruoci.redis.utils.DateUtils;
import com.ruoci.redis.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * @Author yang
 * 位存储
 * 用户在线状态
 * 统计用户的登陆信息,员工的打卡信息等等。
 * 只要是事物只有两个状态的,我们都可以用Bitmap来进行操作!
 **/
@Slf4j
@RestController
@RequestMapping("/bitmap")
public class BitmapController {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 用户签到
     * @return
     */
    @GetMapping("/sign")
    public BasicResponse bitmap(){
        //1. 登录用户id
        Long userId = 10005L;
        //2. keySuffix(:年月)
        String keySuffix = DateUtil.format(new Date(), BusinessConstants.USER_SIGN_FORMAT);
        //3.拼接key(sign:userId:年月)
        String key = BusinessConstants.USER_SIGN_KEY.concat(String.valueOf(userId)).concat(keySuffix);
        //4. 获取今天是本月的第几天
        int day = DateUtil.dayOfMonth(new Date());
        //5. 写入redis setBit key offset 1
        redisTemplate.opsForValue().setBit(key, day - 1, true);
        return BasicResponse.success(new Sign(ResponseCode.SUCCESS.getCode(),BusinessConstants.SUCCESS_SIGNED_IN));
    }


    /**
     * 查询当天是否有签到
     * @param uid
     * @return
     */
    @GetMapping("/checkSign")
    public BasicResponse bitmap(@RequestParam String uid){
        //1.获取组合key
        String signDate = DateUtil.date().toString();
        String signKey = RedisUtil.getSignKey(uid, signDate);
        //2.获取当前时间是这个月的第几天
        int day = DateUtil.dayOfMonth(DateUtil.parse(signDate))-1;
        Boolean bit = redisTemplate.opsForValue().getBit(signKey, day);
        return BasicResponse.success(new Sign(bit,BusinessConstants.CHECK_IN_OR_NOT));
    }


    /**
     * 获取当月首次签到日期
     * @param uid  用户ID
     * @return 首次签到日期
     */
    @GetMapping("/getFirstSignDate")
    public BasicResponse getFirstSignDate(@RequestParam String uid) {
        String keySuffix = DateUtil.format(new Date(), BusinessConstants.USER_SIGN_FORMAT);
        String key = BusinessConstants.USER_SIGN_KEY.concat(String.valueOf(uid)).concat(keySuffix);
        long bitPosition = (Long) redisTemplate.execute((RedisCallback) cbk -> cbk.bitPos(key.getBytes(), true));
        return BasicResponse.success(new Sign(bitPosition < 0 ? null : bitPosition+1,BusinessConstants.FIRST_CHECK_IN_MONTH));
    }

    /**
     * 获取当月签到总次数
     * @param uid
     * @return
     */
    @GetMapping("/getMonthCount")
    public BasicResponse getMonthCount(@RequestParam String uid) {
        String keySuffix = DateUtil.format(new Date(), BusinessConstants.USER_SIGN_FORMAT);
        String signKey = BusinessConstants.USER_SIGN_KEY.concat(String.valueOf(uid)).concat(keySuffix);
        Object count = redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(signKey.getBytes()));
        return BasicResponse.success(new Sign(count,BusinessConstants.TOTAL_CHECK_IN_MONTH));
    }


    /**
     * 获取当前时间在本月连续签到的次数
     */
    @GetMapping("/continuousSign")
    public BasicResponse getContinuousSign(@RequestParam String uid){
        String signDate = DateUtil.date().toString();
        String signKey = RedisUtil.getSignKey(uid, signDate);
        // 获取当前日期是当月的第多少天
        int dayOfMonth = DateUtil.dayOfMonth(new Date());
        // 获取本月截止今天为止的所有签到记录，返回的是一个十进制的数字
        List<Long> result = redisTemplate.opsForValue().bitField(signKey, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        log.info("查询数据:{}", result);
        if (result == null || result.isEmpty()) {
            return BasicResponse.success(0);
        }
        Long number = result.get(0);
        int count = 0;
        if (number == null || number == 0) {
            return BasicResponse.success(0);
        }
        while (true) {
            // 让这个数字与1做与运算，得到数字的最后一位bit，并判断这个bit是否为0
            if ((number & 1) == 0) {
                // 如果为0说明不是连续签到，直接终止
                break;
            } else {
                // 如果不为0，那就为1，说明有签到，继续下次循环，并且计数器加一
                count++;
            }
            // 把数字右移一位，抛弃最后一位的bit，继续下一个bit
            number = number >>> 1;
        }
        return  BasicResponse.success(new Sign(count,BusinessConstants.CONSECUTIVE_CHECK_IN_MONTH));
    }


    /**
     * 获取本月签到信息
     * @param uid
     * @param signDate
     * @return
     */
    @GetMapping("/getSignInfoOfMonth")
    public Map<String, String> getSignInfoOfMonth(@RequestParam String uid, @RequestParam String signDate) {
        String signKey = RedisUtil.getSignKey(uid, signDate);
        int dayOfMonth = DateUtil.dayOfMonth(DateUtil.parse(signDate));
        Map<String, String> signMap = new LinkedHashMap<>(dayOfMonth);
        int lengthOfMonth = DateUtils.lengthOfMonth(DateUtil.parse(signDate));
        //获取Bitmap中的bit数组，并以十进制返回
        List<Long> bitFieldList = redisTemplate.execute((RedisCallback<List<Long>>) cbk
                -> cbk.bitField(signKey.getBytes(), BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(lengthOfMonth)).valueAt(0)));
        if (bitFieldList != null && bitFieldList.size() > 0) {
            long valueDec = bitFieldList.get(0) != null ? bitFieldList.get(0) : 0;
            //使用i--,从最低位开始处理
            for (int i = lengthOfMonth; i > 0; i--) {
                LocalDate tempDayOfMonth = LocalDate.now().withDayOfMonth(i);
                //valueDec先右移1位再左移1位得到一个新值,这个新值最低位的二进制值为0,再与valueDec做比较,如果相等valueDec的最低为是0,否则是1
                if (valueDec >> 1 << 1 != valueDec) {
                    signMap.put(tempDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), "1");
                } else {
                    signMap.put(tempDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), "0");
                }
                //每次处理完右移1位
                valueDec >>= 1;
            }
        }
        return signMap;
    }

}
